package com.masq.aboard.common.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetails;

import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类，用于生成和解析JSON Web Token。
 * @author masq
 * @since 2024-05-01
 */
@Configuration
@ConfigurationProperties(prefix = "jwt")
public class JwtTokenProvider {

    private String secret;

    private String issue;

    private Long expire;

    /**
     * 创建基于用户用户名的token。
     * @param userDetails 用户信息。
     * @return 返回生成的token字符串。
     */
    public String createToken(UserDetails userDetails) {
        // 准备token中要包含的用户信息
        Map<String, Object> claims = new HashMap<>();
        claims.put("username", userDetails.getUsername());
        claims.put("roles", userDetails.getAuthorities());
        // 调用另一个createToken方法，传入用户信息claims，生成并返回token
        return createToken(claims, userDetails.getUsername());
    }

    /**
     * 验证JWT Token的有效性。
     *
     * @param jwtToken 用于验证的JWT Token字符串。
     * @param userDetails 用户详情对象，包含用户名等信息。
     * @return 返回一个布尔值，如果Token有效且未过期，则返回true；否则返回false。
     */
    public boolean validateToken(String jwtToken, UserDetails userDetails) {
        // 验证Token中的用户名是否与userDetails中的用户名一致，并检查Token是否过期
        return getUsername(jwtToken).equals(userDetails.getUsername()) && !isTokenExpired(jwtToken);
    }


    /**
     * 校验token是否过期
     * @param token 用于获取用户信息的令牌
     * @return 如果令牌已过期，则返回true；否则返回false。
     */
    public boolean isTokenExpired(String token) {
        try {
            // 获取token的过期时间
            Date expiration = getClaims(token).getExpiration();
            // 当前时间是否大于过期时间
            return expiration.before(new Date());
        } catch (Exception e) {
            return true;
        }
    }

    /**
     * 根据提供的token获取用户名。
     * <p>
     * 这个方法通过解析token来获取其中的用户名信息。
     *
     * @param token 用于获取用户名的令牌。这是一个代表用户身份的字符串。
     * @return 返回从token中解析出的用户名。返回的用户名为String类型。
     */
    public String getUsername(String token) {
        // 通过token获取claims，并从中提取出用户名
        Claims claims = getClaims(token);
        return claims.get("username").toString();
    }


    /**
     * 从JWT中解析声明信息。
     *
     * @param token 用户的JWT令牌。
     * @return 解析后的声明信息（Claims）对象。
     */
    private Claims getClaims(String token) {
        // 使用JWT解析器构建器设置签名密钥，并构建解析器
        return Jwts.parserBuilder()
                .setSigningKey(generateSecretKey()) // 设置用于验证JWT签名的密钥
                .build()
                .parseClaimsJws(token) // 解析JWT为Claims JWS对象
                .getBody(); // 获取Claims主体
    }


    /**
     * 创建一个JWT令牌。
     *
     * @param claims 令牌中携带的自定义声明信息。
     * @return 生成的JWT令牌字符串。
     */
    private String createToken(Map<String, Object> claims, String subject) {
        // 获取当前时间
        Date now = new Date();
        return Jwts.builder()
                .setIssuer(issue) // 设置发行者
                .setIssuedAt(now) // 设置令牌发行时间
                .setSubject(subject)
                .setClaims(claims) // 设置自定义声明
                .setExpiration(new Date(now.getTime() + expire * 1000)) // 设置令牌过期时间
                .signWith(generateSecretKey()) // 使用动态生成的密钥签名
                .compact(); // 将令牌信息紧凑地编码成字符串
    }


    /**
     * 生成SecretKey的私有方法。
     * <p>此方法通过Base64解码预定义的secret字符串，然后使用解码后的字节数据生成一个HMAC-SHA密钥。</p>
     *
     * @return 返回生成的SecretKey实例。
     * @throws SecurityException 如果解码或密钥生成过程中发生错误，抛出此异常。
     */
    private SecretKey generateSecretKey() {
        try {
            // 使用Base64解码secret字符串
            byte[] decodedKey = Base64.getDecoder().decode(secret);
            // 用解码后的字节数据生成HMAC-SHA密钥
            return Keys.hmacShaKeyFor(decodedKey);
        } catch (IllegalArgumentException e) {
            // 如果有异常发生，抛出安全异常
            throw new SecurityException("生成secretKey失败", e);
        }
    }



    public String getSecret() {
        return secret;
    }

    public void setSecret(String secret) {
        this.secret = secret;
    }

    public String getIssue() {
        return issue;
    }

    public void setIssue(String issue) {
        this.issue = issue;
    }

    public Long getExpire() {
        return expire;
    }

    public void setExpire(Long expire) {
        this.expire = expire;
    }

}
